﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Linq.Expressions;
using ExpressionEvaluator;

namespace ExpressionEvalTest
{
	public class Encapsulate1
	{
		public Encapsulate1()
		{
			Inner = new Encapsulate2();
		}
		public Encapsulate2 Inner
		{
			get;
			set;
		}

		public int IntVal
		{
			get;
			set;
		}
	}

	public class Encapsulate2
	{
		public int IntVal
		{
			get;
			set;
		}
	}
	[TestClass]
	public class EvalizatorTest
	{


		[TestMethod]
		public void SimpleLambdas()
		{
			CompareLambdaWithEvalizer<Func<int>>(() => 5 * 2, null);
		}

		[TestMethod]
		public void LambdasWithVariables()
		{
			int x = 3;
			int y = 3;
		    long longNumber = 4l * 1024 * 1024 * 1024;
			double z = 2.2;
			bool boolValue = false;
			string stringValue = "Test";
            string stringValueWithSlashes = @"\\server\unc_path\Test.pst";

			CompareLambdaWithEvalizer<Func<int>>(() => 5 * x, null);

            CompareLambdaWithEvalizer<Func<long>>(() => 5 * longNumber, null);
			
			CompareLambdaWithEvalizer<Func<int>>(() => 5 * x / y, null);

            CompareLambdaWithEvalizer<Func<bool>>(() => !(1 == x), null);
			
			CompareLambdaWithEvalizer<Func<double>>(() => 5 * x / y * z, null);

			CompareLambdaWithEvalizer<Func<bool>>(() => (stringValue.IndexOf("T") == 0) == boolValue, null);

            CompareLambdaWithEvalizer<Func<string>>(() => stringValueWithSlashes, null);
		}

		[TestMethod]
		public void LambdasValueEncapsulate()
		{
			var temp = new Encapsulate1();
			temp.IntVal = 2;
			temp.Inner.IntVal = 3;

			CompareLambdaWithEvalizer<Func<Encapsulate1, int>>((encap1) => encap1.IntVal * encap1.Inner.IntVal, temp);

			CompareLambdaWithEvalizer<Func<Encapsulate1, int>>((encap1) => encap1.IntVal * temp.Inner.IntVal, temp);
		}




		private void CompareLambdaWithEvalizer<TDelegate>(Expression<TDelegate> lambda, params object[] functionValues)
		{

			object lambdaResult = (lambda as LambdaExpression).Compile().DynamicInvoke(functionValues);

			string code = string.Empty;
			try
			{
				// test evalizer by serializing and deserializing lambda to text and back
				Evalizer evalizer = new Evalizer()
					.AddLookupNamespace("System.Linq")
					.AddLookupNamespace("System.Core")
					.AddLookupNamespace("ExpressionEvalTest")
					.AddLookupTypes(new Type[] { typeof(Encapsulate1), typeof(Encapsulate2) });
				code = evalizer.Evalize(lambda);
				var EvalizerEvaluator = new ExpressionEval(code);
				EvalizerEvaluator.AddLookupAssembly("System.Core");
				EvalizerEvaluator.AddLookupNamespace("ExpressionEvalTest");
				EvalizerEvaluator.AddExtension("System.Linq", "IQueryable`1");
				EvalizerEvaluator.AddExtension("System.Linq", "IEnumerable`1");
				if (lambda.Parameters != null)
				{
					foreach (ParameterExpression param in lambda.Parameters)
					{
						EvalizerEvaluator.AddParam(param);
					}
				}
				LambdaExpression evalizerEval;

				evalizerEval = EvalizerEvaluator.Eval<TDelegate>();

				object evalizerResult = evalizerEval.Compile().DynamicInvoke(functionValues);
				if ((lambdaResult is System.Collections.IEnumerable) && (evalizerResult is System.Collections.IEnumerable))
				{
					var lambdaE = lambdaResult as System.Collections.IEnumerable;
					var evalE = (evalizerResult as System.Collections.IEnumerable).GetEnumerator();
					evalE.Reset();

					foreach (var item in lambdaE)
					{
						evalE.MoveNext();
						var evalItem = evalE.Current;
						Assert.AreEqual(item, evalItem,
					string.Format("Eval item: {0}\nLambda item: {1}", evalItem.ToString(), item.ToString()));
					}
				}
				else
				{
					Assert.AreEqual(lambdaResult, evalizerResult,
						string.Format("Code: {0}\nEval: {1}\nLambda: {2}", code, evalizerEval.ToString(), lambda.ToString()));
				}
			}
			catch (Exception e)
			{
				Trace.WriteLine(string.Format("Code: {0}; Lambda type:{1}; Exception:{2}",
					code, typeof(TDelegate).ToString(), e.ToString()));
				Debug.WriteLine(string.Format("Code: {0}; Lambda type:{1}; Exception:{2}",
					code, typeof(TDelegate).ToString(), e.ToString()));
				throw;
			}
		}


	}

	public class LambdaTestEvalizer
	{
		public int FunctionTest(int x, int y)
		{
			return x + y;
		}
		public bool Test(Func<int, bool> lambda)
		{
			return lambda(1);
		}
	}
	public enum TestEnumEvalizer
	{
		FirstItem,
		SecondItem
	}
}
